// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Html;
using System.Diagnostics;
using System.Globalization;

namespace Microsoft.AspNetCore.Mvc.ViewFeatures;

/// <summary>
/// Information about the current &lt;form&gt;.
/// </summary>
/// <remarks>
/// Literal &lt;form&gt; elements in a view will share the default <see cref="FormContext"/> instance unless tag
/// helpers are enabled.
/// </remarks>
[DebuggerDisplay("FormData = {FormData.Count}")]
public class FormContext
{
    private Dictionary<string, bool> _renderedFields;
    private Dictionary<string, bool> _invariantFields;
    private Dictionary<string, object> _formData;
    private IList<IHtmlContent> _endOfFormContent;

    /// <summary>
    /// Gets a property bag for any information you wish to associate with a &lt;form/&gt; in an
    /// <see cref="Rendering.IHtmlHelper"/> implementation or extension method.
    /// </summary>
    public IDictionary<string, object> FormData
    {
        get
        {
            if (_formData == null)
            {
                _formData = new Dictionary<string, object>(StringComparer.Ordinal);
            }

            return _formData;
        }
    }

    /// <summary>
    /// Gets or sets an indication the current &lt;form&gt; element contains an antiforgery token. Do not use
    /// unless <see cref="CanRenderAtEndOfForm"/> is <c>true</c>.
    /// </summary>
    /// <value>
    /// <c>true</c> if the current &lt;form&gt; element contains an antiforgery token; <c>false</c> otherwise.
    /// </value>
    public bool HasAntiforgeryToken { get; set; }

    /// <summary>
    /// Gets an indication the <see cref="FormData"/> property bag has been used and likely contains entries.
    /// </summary>
    /// <value>
    /// <c>true</c> if the backing field for <see cref="FormData"/> is non-<c>null</c>; <c>false</c> otherwise.
    /// </value>
    public bool HasFormData => _formData != null;

    /// <summary>
    /// Gets an indication the <see cref="EndOfFormContent"/> collection has been used and likely contains entries.
    /// </summary>
    /// <value>
    /// <c>true</c> if the backing field for <see cref="EndOfFormContent"/> is non-<c>null</c>; <c>false</c>
    /// otherwise.
    /// </value>
    public bool HasEndOfFormContent => _endOfFormContent != null;

    /// <summary>
    /// Gets an <see cref="IHtmlContent"/> collection that should be rendered just prior to the next &lt;/form&gt;
    /// end tag. Do not use unless <see cref="CanRenderAtEndOfForm"/> is <c>true</c>.
    /// </summary>
    public IList<IHtmlContent> EndOfFormContent
    {
        get
        {
            if (_endOfFormContent == null)
            {
                _endOfFormContent = new List<IHtmlContent>();
            }

            return _endOfFormContent;
        }
    }

    /// <summary>
    /// Gets or sets an indication whether extra content can be rendered at the end of the content of this
    /// &lt;form&gt; element. That is, <see cref="EndOfFormContent"/> will be rendered just prior to the
    /// &lt;/form&gt; end tag.
    /// </summary>
    /// <value>
    /// <c>true</c> if the framework will render <see cref="EndOfFormContent"/>; <c>false</c> otherwise. In
    /// particular, <c>true</c> if the current &lt;form&gt; is associated with a tag helper or will be generated by
    /// an HTML helper; <c>false</c> when using the default <see cref="FormContext"/> instance.
    /// </value>
    public bool CanRenderAtEndOfForm { get; set; }

    /// <summary>
    /// Gets a dictionary mapping full HTML field names to indications that the named field has been rendered in
    /// this &lt;form&gt;.
    /// </summary>
    private Dictionary<string, bool> RenderedFields
    {
        get
        {
            if (_renderedFields == null)
            {
                _renderedFields = new Dictionary<string, bool>(StringComparer.Ordinal);
            }

            return _renderedFields;
        }
    }

    /// <summary>
    /// Gets a dictionary mapping full HTML field names to indications that corresponding value was formatted
    /// using <see cref="CultureInfo.InvariantCulture"/>.
    /// </summary>
    private Dictionary<string, bool> InvariantFields
    {
        get
        {
            _invariantFields ??= new(StringComparer.Ordinal);
            return _invariantFields;
        }
    }

    /// <summary>
    /// Returns an indication based on <see cref="RenderedFields"/> that the given <paramref name="fieldName"/> has
    /// been rendered in this &lt;form&gt;.
    /// </summary>
    /// <param name="fieldName">The full HTML name of a field that may have been rendered.</param>
    /// <returns>
    /// <c>true</c> if the given <paramref name="fieldName"/> has been rendered; <c>false</c> otherwise.
    /// </returns>
    public bool RenderedField(string fieldName)
    {
        ArgumentNullException.ThrowIfNull(fieldName);

        bool result;
        RenderedFields.TryGetValue(fieldName, out result);

        return result;
    }

    /// <summary>
    /// Updates <see cref="RenderedFields"/> to indicate <paramref name="fieldName"/> has been rendered in this
    /// &lt;form&gt;.
    /// </summary>
    /// <param name="fieldName">The full HTML name of a field that may have been rendered.</param>
    /// <param name="value">If <c>true</c>, the given <paramref name="fieldName"/> has been rendered.</param>
    public void RenderedField(string fieldName, bool value)
    {
        ArgumentNullException.ThrowIfNull(fieldName);

        RenderedFields[fieldName] = value;
    }

    internal bool InvariantField(string fieldName)
    {
        ArgumentNullException.ThrowIfNull(fieldName);

        InvariantFields.TryGetValue(fieldName, out var result);

        return result;
    }

    internal void InvariantField(string fieldName, bool value)
    {
        ArgumentNullException.ThrowIfNull(fieldName);

        InvariantFields[fieldName] = value;
    }
}
